import { BOOTSTRAP_PATH } from '../../lib/utils';
import { WebDriverAgent } from '../../lib/webdriveragent';
import * as utils from '../../lib/utils';
import path from 'path';
import _ from 'lodash';
import sinon from 'sinon';

const fakeConstructorArgs = {
  device: {
    udid: 'some-sim-udid',
    simctl: {},
    devicectl: {},
    idb: null
  },
  platformVersion: '9',
  host: 'me',
  port: '5000',
  realDevice: false
};

const defaultAgentPath = path.resolve(BOOTSTRAP_PATH, 'WebDriverAgent.xcodeproj');
const customBootstrapPath = '/path/to/wda';
const customAgentPath = '/path/to/some/agent/WebDriverAgent.xcodeproj';
const customDerivedDataPath = '/path/to/some/agent/DerivedData/';

describe('Constructor', function () {
  let chai;

  before(async function() {
    chai = await import('chai');
    const chaiAsPromised = await import('chai-as-promised');

    chai.should();
    chai.use(chaiAsPromised.default);
  });

  it('should have a default wda agent if not specified', function () {
    let agent = new WebDriverAgent({}, fakeConstructorArgs);
    agent.bootstrapPath.should.eql(BOOTSTRAP_PATH);
    agent.agentPath.should.eql(defaultAgentPath);
  });
  it('should have custom wda bootstrap and default agent if only bootstrap specified', function () {
    let agent = new WebDriverAgent({}, _.defaults({
      bootstrapPath: customBootstrapPath,
    }, fakeConstructorArgs));
    agent.bootstrapPath.should.eql(customBootstrapPath);
    agent.agentPath.should.eql(path.resolve(customBootstrapPath, 'WebDriverAgent.xcodeproj'));
  });
  it('should have custom wda bootstrap and agent if both specified', function () {
    let agent = new WebDriverAgent({}, _.defaults({
      bootstrapPath: customBootstrapPath,
      agentPath: customAgentPath,
    }, fakeConstructorArgs));
    agent.bootstrapPath.should.eql(customBootstrapPath);
    agent.agentPath.should.eql(customAgentPath);
  });
  it('should have custom derivedDataPath if specified', function () {
    let agent = new WebDriverAgent({}, _.defaults({
      derivedDataPath: customDerivedDataPath
    }, fakeConstructorArgs));
    agent.xcodebuild.derivedDataPath.should.eql(customDerivedDataPath);
  });
});

describe('launch', function () {
  it('should use webDriverAgentUrl override and return current status', async function () {
    const override = 'http://mockurl:8100/';
    const args = Object.assign({}, fakeConstructorArgs);
    args.webDriverAgentUrl = override;
    const agent = new WebDriverAgent({}, args);
    const wdaStub = sinon.stub(agent, 'getStatus');
    wdaStub.callsFake(function () {
      return {build: 'data'};
    });

    await agent.launch('sessionId').should.eventually.eql({build: 'data'});
    agent.url.href.should.eql(override);
    agent.jwproxy.server.should.eql('mockurl');
    agent.jwproxy.port.should.eql(8100);
    agent.jwproxy.base.should.eql('');
    agent.jwproxy.scheme.should.eql('http');
    agent.noSessionProxy.server.should.eql('mockurl');
    agent.noSessionProxy.port.should.eql(8100);
    agent.noSessionProxy.base.should.eql('');
    agent.noSessionProxy.scheme.should.eql('http');
    wdaStub.reset();
  });
});

describe('use wda proxy url', function () {
  it('should use webDriverAgentUrl wda proxy url', async function () {
    const override = 'http://127.0.0.1:8100/aabbccdd';
    const args = Object.assign({}, fakeConstructorArgs);
    args.webDriverAgentUrl = override;
    const agent = new WebDriverAgent({}, args);
    const wdaStub = sinon.stub(agent, 'getStatus');
    wdaStub.callsFake(function () {
      return {build: 'data'};
    });

    await agent.launch('sessionId').should.eventually.eql({build: 'data'});

    agent.url.port.should.eql('8100');
    agent.url.hostname.should.eql('127.0.0.1');
    agent.url.path.should.eql('/aabbccdd');
    agent.jwproxy.server.should.eql('127.0.0.1');
    agent.jwproxy.port.should.eql(8100);
    agent.jwproxy.base.should.eql('/aabbccdd');
    agent.jwproxy.scheme.should.eql('http');
    agent.noSessionProxy.server.should.eql('127.0.0.1');
    agent.noSessionProxy.port.should.eql(8100);
    agent.noSessionProxy.base.should.eql('/aabbccdd');
    agent.noSessionProxy.scheme.should.eql('http');
  });
});

describe('get url', function () {
  it('should use default WDA listening url', function () {
    const args = Object.assign({}, fakeConstructorArgs);
    const agent = new WebDriverAgent({}, args);
    agent.url.href.should.eql('http://127.0.0.1:8100/');
    agent.setupProxies('mysession');
    agent.jwproxy.scheme.should.eql('http');
    agent.noSessionProxy.scheme.should.eql('http');
  });
  it('should use default WDA listening url with emply base url', function () {
    const wdaLocalPort = '9100';
    const wdaBaseUrl = '';

    const args = Object.assign({}, fakeConstructorArgs);
    args.wdaBaseUrl = wdaBaseUrl;
    args.wdaLocalPort = wdaLocalPort;

    const agent = new WebDriverAgent({}, args);
    agent.url.href.should.eql('http://127.0.0.1:9100/');
    agent.setupProxies('mysession');
    agent.jwproxy.scheme.should.eql('http');
    agent.noSessionProxy.scheme.should.eql('http');
  });
  it('should use customised WDA listening url', function () {
    const wdaLocalPort = '9100';
    const wdaBaseUrl = 'http://mockurl';

    const args = Object.assign({}, fakeConstructorArgs);
    args.wdaBaseUrl = wdaBaseUrl;
    args.wdaLocalPort = wdaLocalPort;

    const agent = new WebDriverAgent({}, args);
    agent.url.href.should.eql('http://mockurl:9100/');
    agent.setupProxies('mysession');
    agent.jwproxy.scheme.should.eql('http');
    agent.noSessionProxy.scheme.should.eql('http');
  });
  it('should use customised WDA listening url with slash', function () {
    const wdaLocalPort = '9100';
    const wdaBaseUrl = 'http://mockurl/';

    const args = Object.assign({}, fakeConstructorArgs);
    args.wdaBaseUrl = wdaBaseUrl;
    args.wdaLocalPort = wdaLocalPort;

    const agent = new WebDriverAgent({}, args);
    agent.url.href.should.eql('http://mockurl:9100/');
    agent.setupProxies('mysession');
    agent.jwproxy.scheme.should.eql('http');
    agent.noSessionProxy.scheme.should.eql('http');
  });
  it('should use the given webDriverAgentUrl and ignore other params', function () {
    const args = Object.assign({}, fakeConstructorArgs);
    args.wdaBaseUrl = 'http://mockurl/';
    args.wdaLocalPort = '9100';
    args.webDriverAgentUrl = 'https://127.0.0.1:8100/';

    const agent = new WebDriverAgent({}, args);
    agent.url.href.should.eql('https://127.0.0.1:8100/');
  });
  it('should set scheme to https for https webDriverAgentUrl', function () {
    const args = Object.assign({}, fakeConstructorArgs);
    args.webDriverAgentUrl = 'https://127.0.0.1:8100/';
    const agent = new WebDriverAgent({}, args);
    agent.setupProxies('mysession');
    agent.jwproxy.scheme.should.eql('https');
    agent.noSessionProxy.scheme.should.eql('https');
  });
});

describe('setupCaching()', function () {
  let wda;
  let wdaStub;
  let wdaStubUninstall;
  const getTimestampStub = sinon.stub(utils, 'getWDAUpgradeTimestamp');

  beforeEach(function () {
    wda = new WebDriverAgent('1', fakeConstructorArgs);
    wdaStub = sinon.stub(wda, 'getStatus');
    wdaStubUninstall = sinon.stub(wda, 'uninstall');
  });

  afterEach(function () {
    for (const stub of [wdaStub, wdaStubUninstall, getTimestampStub]) {
      if (stub) {
        stub.reset();
      }
    }
  });

  it('should not call uninstall since no Running WDA', async function () {
    wdaStub.callsFake(function () {
      return null;
    });
    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.notCalled.should.be.true;
    _.isUndefined(wda.webDriverAgentUrl).should.be.true;
  });

  it('should not call uninstall since running WDA has only time', async function () {
    wdaStub.callsFake(function () {
      return {build: { time: 'Jun 24 2018 17:08:21' }};
    });
    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.notCalled.should.be.true;
    wda.webDriverAgentUrl.should.equal('http://127.0.0.1:8100/');
  });

  it('should call uninstall once since bundle id is not default without updatedWDABundleId capability', async function () {
    wdaStub.callsFake(function () {
      return {build: { time: 'Jun 24 2018 17:08:21', productBundleIdentifier: 'com.example.WebDriverAgent' }};
    });
    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.calledOnce.should.be.true;
    _.isUndefined(wda.webDriverAgentUrl).should.be.true;
  });

  it('should call uninstall once since bundle id is different with updatedWDABundleId capability', async function () {
    wdaStub.callsFake(function () {
      return {build: { time: 'Jun 24 2018 17:08:21', productBundleIdentifier: 'com.example.different.WebDriverAgent' }};
    });

    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.calledOnce.should.be.true;
    _.isUndefined(wda.webDriverAgentUrl).should.be.true;
  });

  it('should not call uninstall since bundle id is equal to updatedWDABundleId capability', async function () {
    wda = new WebDriverAgent('1', { ...fakeConstructorArgs, updatedWDABundleId: 'com.example.WebDriverAgent' });
    wdaStub = sinon.stub(wda, 'getStatus');
    wdaStubUninstall = sinon.stub(wda, 'uninstall');

    wdaStub.callsFake(function () {
      return {build: { time: 'Jun 24 2018 17:08:21', productBundleIdentifier: 'com.example.WebDriverAgent' }};
    });

    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.notCalled.should.be.true;
    wda.webDriverAgentUrl.should.equal('http://127.0.0.1:8100/');
  });

  it('should call uninstall if current revision differs from the bundled one', async function () {
    wdaStub.callsFake(function () {
      return {build: { upgradedAt: '1' }};
    });
    getTimestampStub.callsFake(() => '2');
    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.calledOnce.should.be.true;
  });

  it('should not call uninstall if current revision is the same as the bundled one', async function () {
    wdaStub.callsFake(function () {
      return {build: { upgradedAt: '1' }};
    });
    getTimestampStub.callsFake(() => '1');
    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.notCalled.should.be.true;
  });

  it('should not call uninstall if current revision cannot be retrieved from WDA status', async function () {
    wdaStub.callsFake(function () {
      return {build: {}};
    });
    getTimestampStub.callsFake(() => '1');
    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.notCalled.should.be.true;
  });

  it('should not call uninstall if current revision cannot be retrieved from the file system', async function () {
    wdaStub.callsFake(function () {
      return {build: { upgradedAt: '1' }};
    });
    getTimestampStub.callsFake(() => null);
    wdaStubUninstall.callsFake(_.noop);

    await wda.setupCaching();
    wdaStub.calledOnce.should.be.true;
    wdaStubUninstall.notCalled.should.be.true;
  });

  describe('uninstall', function () {
    let device;
    let wda;
    let deviceGetBundleIdsStub;
    let deviceRemoveAppStub;

    beforeEach(function () {
      device = {
        getUserInstalledBundleIdsByBundleName: () => {},
        removeApp: () => {}
      };
      wda = new WebDriverAgent('1', {device});
      deviceGetBundleIdsStub = sinon.stub(device, 'getUserInstalledBundleIdsByBundleName');
      deviceRemoveAppStub = sinon.stub(device, 'removeApp');
    });

    afterEach(function () {
      for (const stub of [deviceGetBundleIdsStub, deviceRemoveAppStub]) {
        if (stub) {
          stub.reset();
        }
      }
    });

    it('should not call uninstall', async function () {
      deviceGetBundleIdsStub.callsFake(() => []);

      await wda.uninstall();
      deviceGetBundleIdsStub.calledOnce.should.be.true;
      deviceRemoveAppStub.notCalled.should.be.true;
    });

    it('should call uninstall once', async function () {
      const uninstalledBundIds = [];
      deviceGetBundleIdsStub.callsFake(() => ['com.appium.WDA1']);
      deviceRemoveAppStub.callsFake((id) => uninstalledBundIds.push(id));

      await wda.uninstall();
      deviceGetBundleIdsStub.calledOnce.should.be.true;
      deviceRemoveAppStub.calledOnce.should.be.true;
      uninstalledBundIds.should.eql(['com.appium.WDA1']);
    });

    it('should call uninstall twice', async function () {
      const uninstalledBundIds = [];
      deviceGetBundleIdsStub.callsFake(() => ['com.appium.WDA1', 'com.appium.WDA2']);
      deviceRemoveAppStub.callsFake((id) => uninstalledBundIds.push(id));

      await wda.uninstall();
      deviceGetBundleIdsStub.calledOnce.should.be.true;
      deviceRemoveAppStub.calledTwice.should.be.true;
      uninstalledBundIds.should.eql(['com.appium.WDA1', 'com.appium.WDA2']);
    });
  });
});


describe('usePreinstalledWDA related functions', function () {
  describe('bundleIdForXctest', function () {
    it('should have xctrunner automatically', function () {
      const args = Object.assign({}, fakeConstructorArgs);
      args.updatedWDABundleId = 'io.appium.wda';
      const agent = new WebDriverAgent({}, args);
      agent.bundleIdForXctest.should.equal('io.appium.wda.xctrunner');
    });

    it('should have xctrunner automatically with default bundle id', function () {
      const args = Object.assign({}, fakeConstructorArgs);
      const agent = new WebDriverAgent({}, args);
      agent.bundleIdForXctest.should.equal('com.facebook.WebDriverAgentRunner.xctrunner');
    });

    it('should allow an empty string as xctrunner suffix', function () {
      const args = Object.assign({}, fakeConstructorArgs);
      args.updatedWDABundleId = 'io.appium.wda';
      args.updatedWDABundleIdSuffix = '';
      const agent = new WebDriverAgent({}, args);
      agent.bundleIdForXctest.should.equal('io.appium.wda');
    });

    it('should allow an empty string as xctrunner suffix with default bundle id', function () {
      const args = Object.assign({}, fakeConstructorArgs);
      args.updatedWDABundleIdSuffix = '';
      const agent = new WebDriverAgent({}, args);
      agent.bundleIdForXctest.should.equal('com.facebook.WebDriverAgentRunner');
    });

    it('should have an arbitrary xctrunner suffix', function () {
      const args = Object.assign({}, fakeConstructorArgs);
      args.updatedWDABundleId = 'io.appium.wda';
      args.updatedWDABundleIdSuffix = '.customsuffix';
      const agent = new WebDriverAgent({}, args);
      agent.bundleIdForXctest.should.equal('io.appium.wda.customsuffix');
    });

  });
});
